

export class Dispatcher {
    constructor(element) {
        this.element = element;
    }

    dispatch(type, properties) {
        let event = new Event(type);
        for (let name in properties) {
            event[name] = properties[name];
        }
        console.log(event);
        this.element.dispatchEvent(event);
    }
}



// listen => recoginze => dispatch


// new Listener(new Recoginzer(dispatch))

export class Listener {
    constructor(element, recoginzer) {
        let isListeningMouse = false;
          
        let contexts = new Map();

        element.addEventListener('mousedown', e => {
            let context = Object.create(null);
            contexts.set("mouse" + (1 << e.button), context);
        
            recoginzer.start(e, context);

            let mousemove = event => {
                let button = 1;
        
                while(button <= event.buttons) {
                    if (button & event.buttons) {
                        // order of buttons & button property is not same
                        let key;
                        if (button === 2) {
                            key = 4;
                        } else if (button === 4) {
                            key = 2;
                        } else {
                            key = button;
                        }
                        let cont = contexts.get("mouse" + key);
                        recoginzer.move(event, cont);
                    }
                    
                    button = button << 1;
                }
            };
        
        
            let mouseup = event => {
                let cont = contexts.get("mouse" + (1 << event.button));
                recoginzer.end(event, cont);
                contexts.delete(cont);
        
                if (event.button === 0) {
                    document.removeEventListener('mousemove', mousemove);
                    document.removeEventListener('mouseup', mouseup);
                    isListeningMouse = false;
                }
                
            };
        
            if (!isListeningMouse) {
                document.addEventListener('mousemove', mousemove);
                document.addEventListener('mouseup', mouseup);
                isListeningMouse = true;
            }
        
        });

        element.addEventListener('touchstart', e => {
            for (let touch of e.changedTouches) {
                let context = Object.create(null);
                contexts.set(touch.identifier, context);
                recoginzer.start(touch, context);
            }
        
        });
        
        element.addEventListener('touchmove', e => {
            for (let touch of e.changedTouches) {
                let context = contexts.get(touch.identifier);
                recoginzer.move(touch, context);
            }
        
            
        });
        
        element.addEventListener('touchend', e => {
            for (let touch of e.changedTouches) {
                let context = contexts.get(touch.identifier);
                recoginzer.end(touch, context);
                contexts.delete(touch.identifier);
            }
        
            
        });
        
        // 异常结束，例如被弹窗打断
        element.addEventListener('touchcancel', e => {
            for (let touch of e.changedTouches) {
                let context = contexts.get(touch.identifier);
                recoginzer.cancel(touch, context);
                contexts.delete(touch.identifier);
            }
        });
      
    }
}


export class Recoginzer {
    constructor(dispatcher) {
        this.dispatcher = dispatcher;
    }

    start(point, context) {
        context.startX = point.clientX;
        context.startY = point.clientY;
        context.points = [{
            t: Date.now(),
            x: point.clientX,
            y: point.clientY
        }];
    
        context.isPan = false;
        context.isTap = true;
        context.isPress = false;
    
        context.handler = setTimeout(() => {
            context.isPan = false;
            context.isTap = false;
            context.isPress = true;
            context.handler = null;
            this.dispatcher.dispatch("press", {});
        }, 500);
    }

    move(point, context) {
        let dx = point.clientX - context.startX;
        let dy = point.clientY - context.startY;
    
        if (!context.isPan && (dx ** 2 + dy ** 2) > 100) {
            context.isPan = true;
            context.isTap = false;
            context.isPress = false;
            context.isVertical = Math.abs(dx) < Math.abs(dy);
            this.dispatcher.dispatch("panstart", {
                startX: context.startX,
                startY: context.startY,
                clientX: point.clientX,
                clientY: point.clientY,
                isVertical: context.isVertical,
            });
            clearTimeout(context.handler);
        }
    
        if (context.isPan) {
            this.dispatcher.dispatch("pan", {
                startX: context.startX,
                startY: context.startY,
                clientX: point.clientX,
                clientY: point.clientY,
                isVertical: context.isVertical,
            });
        }
    
        context.points = context.points.filter(p => Date.now() - p.t < 500);
    
        context.points.push({
            t: Date.now(),
            x: point.clientX,
            y: point.clientY
        });
        // console.log("move:", point.clientX, point.clientY);
    }

    end(point, context) {
        if (context.isTap) {
            this.dispatcher.dispatch('tap', {});
            clearTimeout(context.handler);
        }
        if (context.isPress) {
            this.dispatcher.dispatch('pressend', {});
        }
    
        context.points = context.points.filter(p => Date.now() - p.t < 500);
    
        let d, v;
        if (!context.points.length) {
            v = 0;
        } else {
            d = Math.sqrt((point.clientX - context.points[0].x)**2 + (point.clientY - context.points[0].y)**2);
            v = d / (Date.now() - context.points[0].t);
        }
    
        if (v > 1.5) {
            context.isFlick = true;
            this.dispatcher.dispatch("flick", {
                startX: context.startX,
                startY: context.startY,
                clientX: point.clientX,
                clientY: point.clientY,
                isVertical: context.isVertical,
                isFlick: context.isFlick,
                velocity: v,
            });
        } else {
            context.isFlick = false;
        }

        if (context.isPan) {
            this.dispatcher.dispatch("panend", {
                startX: context.startX,
                startY: context.startY,
                clientX: point.clientX,
                clientY: point.clientY,
                isVertical: context.isVertical,
                isFlick: context.isFlick,
            });
        }
    
        console.log(v);
        // console.log("end:", point.clientX, point.clientY);
    }

    cancel(point, context) {
        clearTimeout(context.handler);
        dispatch('cancel', {});
    }
}


export function enableGesture(element) {
    new Listener(element, new Recoginzer(new Dispatcher(element)));
}